#!/usr/bin/env python
# -*- coding: UTF-8 -*-

'''Experimental: Turn a elementary cellular automata row into sounds'''

import time
import sys

import pygame.midi

from cellular_automata.elementary import ElementaryCellularAutomata

pygame.midi.init()

OCTAVE_MINUS_ONE = {'C': 0, 'C#': 1, 'D': 2, 'D#': 3, 'E': 4, 'F': 5, 'F#': 6, 'G': 7, 'G#': 8, 'A': 9, 'A#': 10, 'B': 11}
OCTAVE_ZERO = {'C': 12, 'C#': 13, 'D': 14, 'D#': 15, 'E': 16, 'F': 17, 'F#': 18, 'G': 19, 'G#': 20, 'A': 21, 'A#': 22, 'B': 23}
OCTAVE_ONE = {'C': 24, 'C#': 25, 'D': 26, 'D#': 27, 'E': 28, 'F': 29, 'F#': 30, 'G': 31, 'G#': 32, 'A': 33, 'A#': 34, 'B': 35}
OCTAVE_TWO = {'C': 36, 'C#': 37, 'D': 38, 'D#': 39, 'E': 40, 'F': 41, 'F#': 42, 'G': 43, 'G#': 44, 'A': 45, 'A#': 46, 'B': 47}
OCTAVE_THREE = {'C': 48, 'C#': 49, 'D': 50, 'D#': 51, 'E': 52, 'F': 53, 'F#': 54, 'G': 55, 'G#': 56, 'A': 57, 'A#': 58, 'B': 59}
OCTAVE_FOUR = {'C': 60, 'C#': 61, 'D': 62, 'D#': 63, 'E': 64, 'F': 65, 'F#': 66, 'G': 67, 'G#': 68, 'A': 69, 'A#': 70, 'B': 71}
OCTAVE_FIVE = {'C': 72, 'C#': 73, 'D': 74, 'D#': 75, 'E': 76, 'F': 77, 'F#': 78, 'G': 79, 'G#': 80, 'A': 81, 'A#': 82, 'B': 83}
OCTAVE_SIX = {'C': 84, 'C#': 85, 'D': 86, 'D#': 87, 'E': 88, 'F': 89, 'F#': 90, 'G': 91, 'G#': 92, 'A': 93, 'A#': 94, 'B': 95}
OCTAVE_SEVEN = {'C': 96, 'C#': 97, 'D': 98, 'D#': 99, 'E': 100, 'F': 101, 'F#': 102, 'G': 103, 'G#': 104, 'A': 105, 'A#': 106, 'B': 107}
OCTAVE_EIGHT = {'C': 108, 'C#': 109, 'D': 110, 'D#': 111, 'E': 112, 'F': 113, 'F#': 114, 'G': 115, 'G#': 116, 'A': 117, 'A#': 118, 'B': 119}
OCTAVE_NINE = {'C': 120, 'C#': 121, 'D': 122, 'D#': 123, 'E': 124, 'F': 125, 'F#': 126, 'G': 127}


class NoteSheet(object):
    def __init__(self, bpm, instrument=19):
        self.notes = []
        self.bpm = bpm
        self.instrument = instrument

        default = pygame.midi.get_default_output_id()
        midiOutput = pygame.midi.Output(default)
        midiOutput.set_instrument(instrument)

        self.midiOutput = midiOutput

        self.octave_mapping = {-1: OCTAVE_MINUS_ONE, 0: OCTAVE_ZERO, 1: OCTAVE_ONE, 2: OCTAVE_TWO, 3: OCTAVE_THREE,
                               4: OCTAVE_FOUR, 5: OCTAVE_FIVE, 6: OCTAVE_SIX, 7: OCTAVE_SEVEN, 8: OCTAVE_EIGHT,
                               9: OCTAVE_NINE}

    def __iadd__(self, item):
        if isinstance(item, Note):
            self.notes.append(item)
        else:
            raise ValueError('NoteSheet can only consist of notes! {0} passed'.format(type(item)))

        return self

    def play_tone(self, tone, octave, length):
        '''tone: A string representation of the tone you want to play, e.g. "C" or "C#"
           octave: A integer representation of the octave the tone should be played in, e.g. 1 or 5
           length: Integer how long the tone should be held in ms'''
        # Get the actual integer value corresponding to the tone/octave pair
        iTone = self.octave_mapping[octave][tone]

        self.midiOutput.note_on(iTone, 127)
        time.sleep(length)
        self.midiOutput.note_off(iTone, 127)

    def play_sheet(self):
        duration = 60000.0 / self.bpm * 4.0 / 1000.0
        print('Notes: {0}'.format(len(self.notes)))
        print('BPM: {0}'.format(self.bpm))
        for n in self.notes:
            note_duration = duration * 1.0 / n.duration
            if n.note == 'P':
                print('Pausing for {0}'.format(note_duration))
                time.sleep(note_duration)
            else:
                print('Playing note {0} in octave {1} with a duration of {2}'.format(n.note, n.octave, note_duration))
                self.play_tone(n.note, n.octave, note_duration)

    def print_sheet(self):
        string = []
        for n in self.notes:
            if n.duration:
                string.append(str(n.duration))
            string.append(str(n.note))
            if n.octave:
                string.append(str(n.octave))
            string.append(' ')

        print(''.join(string))


class Note(object):
    '''Class that represents a note/pause'''
    def __init__(self, duration, note, octave):
        self.duration = int(duration)
        self.note = note
        self.octave = int(octave)

    def __repr__(self):
        return 'Note: {0}; Duration: {1}; Octave: {2}'.format(self.note, self.duration, self.octave)


def row_to_note(row):
    note = Note(1, 'P', 5)

    if row[0] == '1':
        note = Note(1, 'C', 5)
    if row[1] == '1':
        note = Note(1, 'C#', 5)
    if row[2] == '1':
        note = Note(1, 'D', 5)
    if row[3] == '1':
        note = Note(1, 'D#', 5)
    if row[4] == '1':
        note = Note(1, 'E', 5)
    if row[5] == '1':
        note = Note(1, 'F', 5)
    if row[6] == '1':
        note = Note(1, 'F#', 5)
    if row[7] == '1':
        note = Note(1, 'G', 5)
    if row[8] == '1':
        note = Note(1, 'G#', 5)
    if row[9] == '1':
        note = Note(1, 'A', 5)
    if row[10] == '1':
        note = Note(1, 'A#', 5)
    if row[11] == '1':
        note = Note(1, 'B', 5)

    return note

if __name__ == '__main__':

    try:
        rule = int(sys.argv[1])
    except:
        rule = 150

    ca = ElementaryCellularAutomata(rule, '1', 13)

    for _ in range(100):
        ca.game_loop()

    ns = NoteSheet(1200)

    for row in ca.rows:
        ns += row_to_note(row)

    ns.print_sheet()

    ns.play_sheet()
